
// 仿照vue指令  v-model
// 使用方法  绑定DOM
// 也可以主动使用
/* page({
    data:{
        person:{
            name:"wanghuan",
            age:[0,1,2]
        }
    }
}) */
//绑定 input事件
/* <input type="text" 
       bindinput = "$model" 
       data-model = "person.name" || "person.age[0]"
   ></input > */

//绑定点击或其他事件  需要指定 value
/* <view
       bindtap = "$model" 
       data-model = "person.name" || "person.age[0]"
       data-value = "123" || "{{ true || 123 || undefined }}"
   ></view > */

//同时写入对个对象
/* <input type="text" 
       bindinput = "$model" 
       data-model = "person.name,person.age[0]" 
   ></input > */

// 主动调用
// 回调函数modelConfig.callback在  setData之后的  nextTick后调用
/* $model(undefined,{
    "person.name": 'hellow',
    "person.age[0]" : 18 
    // 回调函数
},function(){

}); */
/* 
    2021/3/29 新增特性  -- 写入的value可以为一个 函数  
    函数的第一个参数  为  即将正确写入的 key 之前的值 
    函数的this指向  为  调用$model的对象
    示例如下
    $model(undefined,{
        age( ageOldVal ){
            console.log( ageoldVal + 1 );
            return ageOldVal + 1;
        }
    });
    Page({
        data:{
            age: 1
        }
    })
    正确执行结果应为:
         age === 2

   ###   2022/1/15 新增特性  -- 获取指定载入路径的值 
    
    Page({
        data:{
            list: [1]
            ,age:18
        }
    })      
    const val =  $model(undefined,"list[0]");
    console.log( val ) // 1
    const vals =  $model(undefined,["list[0]","age"]);
    console.log( vals ) // [1 , 18]
  ###   2022/7/19 深度结合 wx.setData函数 优化model  写入速度 
*/
/**
 * @description: 仿照微信小程序model函数(具备其所有特性) ， 并在其之上做了一些扩展。
 * @param {Event}  event  // DOM Event 对象
 * @param {Object} keyObj //自定义密钥配置对象
 * @param {Object} modelConfig  [
 *                     , module // 模块路径会拼接在每个载入路径前面
 *                     , callback // 回调函数，第一个参数为所有载入的集合对象 第二个参数为所有载入对象path:value的集合对象]
 * @return {void}
 */
 const comUtils = require("./common");

 module.exports = function $model(event, keyObj, modelConfig ) {
   let page = this;
   let pageData = page.data;
   let AsskeyObj;
   let keyObjType;
 
   // 载入模式  true === 载入  false === 获取
   let modelType = true;
   // 进行参数合并  两者选其一
   // event优先
   if (event) {
     let eventData = event.currentTarget.dataset;
     let edModel = eventData.model;
     if (edModel) {
       AsskeyObj = {};
       let modelVal;
       // 判断 data-value  属性是否存在
       // 当  ( data-value ="" ) 时也是需要载入
       if (!eventData.hasOwnProperty("value")) {
         modelVal = event.detail.value;
       } else {
         modelVal = eventData.value;
       }
       AsskeyObj[edModel] = modelVal;
     } else {
       throw new Error("the event model is must be required");
     }
   } else {
     if (keyObj) {
       AsskeyObj = {};
       keyObjType = comUtils.toClass(keyObj);
       // 如果是对象模式   默认载入模式 载入
       if (keyObjType === "Object") {
         AsskeyObj = keyObj;
         // 字符串类型 或者 数组类型  载入模式  获取
       } else if (keyObjType === "String") {
         AsskeyObj[keyObj] = function (ov) {
           return ov;
         };
         modelType = false;
       } else if (keyObjType === "Array") {
         keyObj.forEach((keyPath) => {
           AsskeyObj[keyPath] = function (ov) {
             return ov;
           };
         });
         modelType = false;
       }
       if (Object.keys(AsskeyObj).length === 0) {
         throw Error("model object length is null");
       }
     } else {
       throw new Error("the keyObj is must be required");
     }
   }
 
   if (AsskeyObj) {
     // 保存需要写入的所有对象
     // 减少 setData 合并额外没有变动属性
     // setData-path 仅为小程序特性支持
     const modelAss_path = {};
     const modelHasCallback = modelConfig &&  comUtils.isType(modelConfig.callback, "Function");
     Object.keys(AsskeyObj).forEach((keyNode) => {
       // 获取value
       let value = AsskeyObj[keyNode];
       // 2021/3/29 新增 value === function
       const valueIsFun = typeof value === "function";
       let modelPath = keyNode;
       // 判断模块是否存在 为写入路径拼接上模块路径
       if (modelConfig && modelConfig.module && typeof modelConfig.module === "string") {
         modelPath = modelConfig.module + "." + modelPath;
       }
       // 如果存在分割符  , 或者 | 转换为数组
       // 消除空格
       let modelArr = comUtils.trimAll(modelPath).split(/[,|]/);
       // 对分割后的model进行循环写入
       modelArr.forEach((modelNode) => {
         // 如果value不是函数类型 结合setData函数特性  直接收集写入路径   阻断后续查找原始值
         if( !valueIsFun ){
           modelAss_path[modelNode] = value;
           return
         }
         // 转换modelNode上的数组元素为统一分隔符
         let modelAga = modelNode.replace(/(\[)|(\])/gm, (match, p1, p2) => {
           if (p1) {
             return ".";
           } else if (p2) {
             return "";
           } else {
             return match;
           }
         });
         //进行深度写入
         let modelLeve = modelAga.split(".");
         let copyData = pageData;
         for (let j = 0, l = modelLeve.length; j < l; j++) {
           let iterKey = modelLeve[j];
           if (iterKey) {
             let iterObj = copyData[iterKey];
             if (j == l - 1) {
               // 如果value为一个函数  则把 之前的value 作为函数的第一个值
               // 函数 this  指向调用 page对象
               const finalModelVal = copyData[iterKey];
               if (valueIsFun) {
                 copyData[iterKey] = value.call(page, finalModelVal);
               } else {
                 copyData[iterKey] = value;
               }
               // 载入路径收集  ### 仅小程序setData api特性支持
               modelAss_path[modelNode] = copyData[iterKey];
               break;
             } else {
               copyData = iterObj;
             }
           } else {
             throw new Error("the leve is not be Null");
           }
         }
         
       });
     });
 
     // 转换为原生api路径载入
     if (page && typeof page.setData === "function") {
       if (modelType) {
         page.setData(modelAss_path);
       }
       // 成功回调函数
       if (modelHasCallback) {
         wx.nextTick(function () {
           modelConfig.callback( modelAss_path );
         });
       }
       // 如果写入模式返回 写入路径与值 对象
       if (modelType) {
         return modelAss_path;
       } else {
         const modelAssVals = Object.values(modelAss_path);
         // 如果是 获取模式
         // 字符串模式  直接返回获取值
         if (keyObjType === "String") {
           return modelAssVals[0];
         } else {
           // 数组模式   按顺序返回获取值数组集合
           return modelAssVals;
         }
       }
     }
   } else {
     console.warn("missing necessary parameters");
   }
 };
 